/*
    Copyright Pat Niemeyer (pat@pat.net), 2007
    This code is licensed under the GPL.
 */
package quickAdd.client;

import com.google.gwt.user.client.ResponseTextHandler;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.Element;

import java.util.List;
import java.util.ArrayList;

/**
 * A simple model of the Google spreadsheets API and static methods
 * for interacting with GData feeds.
 *
 */
public class GData
{
    public static String WORKSHEETSFEED = "http://schemas.google.com/spreadsheets/2006#worksheetsfeed" ;
    static String CELLSFEED = "http://schemas.google.com/spreadsheets/2006#cellsfeed";
    static String ATOM_CONTENT_TYPE="application/atom+xml";

    public static interface SpreadsheetsListener {
        public void setSpreadsheets( List/*<SpreadSheet>*/ spreadsheets );
    }

    public static interface WorksheetsListener {
        public void setWorksheets( List/*<WorkSheet>*/ worksheets );
        public void setWorksheetMetadata( WorksheetMetadata worksheetMetadata );
    }

    public static interface CellDataListener {
        /**
         * This method returns the actual worksheet row count with data so
         * that the client can gauge whether the spreadsheet row structure
         * has changed.
         * @param cells
         * @param worksheetRowCount
         */
        public void setCellData( CellData cells, int worksheetRowCount );

        public void cellDataError();
    }

    public static interface RowPostedListener {
        public void rowBeginPosting();
        public void rowPostedOk( List values );
        public void rowPostedError();
    }

    public static class Spreadsheet implements Comparable {
        String title;
        String worksheetURL;

        public String toString()
        {
            return "SpreadSheet{" +
                "title='" + title + '\'' +
                ", worksheetURL='" + worksheetURL + '\'' +
                '}';
        }

        public int compareTo( Object o ) {
            return title.compareTo( ((Spreadsheet)o).title );
        }
    }

    public static class Worksheet implements Comparable {
        Spreadsheet spreadsheet;
        String title;
        // We can convert from cellURL to listURL if needed for posting
        String cellFeedURL;

        public String toString()
        {
            return "Worksheet{" +
                "title='" + title + '\'' +
                ", cellURL='" + cellFeedURL + '\'' +
                '}';
        }
        public int compareTo( Object o ) {
            return title.compareTo( ((Worksheet)o).title );
        }
    }

    public static class WorksheetMetadata
    {
        List /*<String>*/ columnNames = new ArrayList();
        Worksheet worksheet;
        int rowCount;
    }

    public static void getSpreadsheetsFeed( Auth auth, final SpreadsheetsListener listener )
    {
        ResponseTextHandler handler = new XMLResponseHandler() {
            public void handle( Element feed )
            {
                List spreadsheets = parseSpreadsheetsFeed( feed );
                QuickAdd.out("spreadsheets = "+spreadsheets );
                listener.setSpreadsheets( spreadsheets );
            }
        };

        String url = "http://spreadsheets.google.com/feeds/spreadsheets/private/full";
        HttpUtil.doAuthorizedGet( auth.getToken(), url, handler );
    }

    public static void getWorksheetsFeed( final Spreadsheet spreadsheet, Auth auth, final WorksheetsListener listener )
    {
        ResponseTextHandler handler = new XMLResponseHandler() {
            public void handle( Element feed ) {
                List worksheets = parseWorksheetsFeed( feed, spreadsheet );
                QuickAdd.out("worksheets = "+worksheets);
                listener.setWorksheets( worksheets );
            }
        };

        HttpUtil.doAuthorizedGet( auth.getToken(), spreadsheet.worksheetURL, handler );
    }

    public static void getHeaderRow(
        Auth auth, final Worksheet worksheet, final WorksheetsListener listener )
    {
        ResponseTextHandler handler = new XMLResponseHandler() {
            public void handle( Element feed )
            {
                QuickAdd.out("parsing celFeed..........");
                GData.WorksheetMetadata worksheetMetadata = parseCellFeedForHeaderRow( feed );
                worksheetMetadata.worksheet = worksheet;
                QuickAdd.out("cells = "+ worksheetMetadata );
                listener.setWorksheetMetadata( worksheetMetadata );
            }
        };
        getCellFeed( auth, 1, 1, worksheet.cellFeedURL, handler );
    }

    public static void getCellData(
        Auth auth, String cellFeedURL, int minRow, int maxRow, final CellDataListener listener )
    {
        ResponseTextHandler handler = new XMLResponseHandler() {
            public void handle( Element feed )
            {
                int actualRowCount = getRowCount( feed );
                CellData cellData = CellData.parse( feed );
                QuickAdd.out( "cells = " + cellData );
                listener.setCellData( cellData, actualRowCount );
            }

            public void handleError( String responseText )
            {
                QuickAdd.out("cell data errror");
                listener.cellDataError();
            }
        };
        getCellFeed( auth, minRow, maxRow, cellFeedURL, handler );
    }

    public static void getCellFeed(
        Auth auth, int minRow, int maxRow, String url, ResponseTextHandler handler )
    {
        url+="?min-row="+minRow+"&max-row="+maxRow;
        HttpUtil.doAuthorizedGet( auth.getToken(), url, handler );
    }

    private static GData.WorksheetMetadata parseCellFeedForHeaderRow(
        Element headerRowFeed )
    {
        GData.WorksheetMetadata worksheetMetadata = new GData.WorksheetMetadata();

        worksheetMetadata.rowCount = getRowCount( headerRowFeed );

        CellData headerCells = CellData.parse( headerRowFeed );
        worksheetMetadata.columnNames = headerCells.getRow(1);

        return worksheetMetadata;
    }

    private static int getRowCount( Element feed ) {
        return Integer.parseInt(
            DOMUtil.getSimpleElementText( feed, "gs:rowCount" ) );
    }

    private static List/*<Spreadsheet>*/ parseSpreadsheetsFeed( Element spreadsheetsFeed )
    {
        List spreadsheets = new ArrayList();
        NodeList entries = spreadsheetsFeed.getElementsByTagName( "entry" );
        for( int i=0; i<entries.getLength(); i++ )
        {
            Spreadsheet ss = new Spreadsheet();
            Element entry = (Element)entries.item( i );
            ss.title = DOMUtil.getSimpleElementText( entry, "title" );

            Element wsLink = DOMUtil.getElementMatching(
                entry, "link", "rel", WORKSHEETSFEED );
            ss.worksheetURL = wsLink.getAttribute( "href" );

            spreadsheets.add(ss);
        }
        return spreadsheets;
    }

    private static List/*<Worksheet>*/ parseWorksheetsFeed(
        Element worksheetsFeed, Spreadsheet spreadsheet )
    {
        List worksheets = new ArrayList();
        QuickAdd.out("getEl by name");
        NodeList entries = worksheetsFeed.getElementsByTagName( "entry" );
        QuickAdd.out("entry nodelist length = "+entries.getLength() );
        for( int i=0; i<entries.getLength(); i++ )
        {
            Worksheet ws = new Worksheet();
            ws.spreadsheet = spreadsheet;
            Element entry = (Element)entries.item( i );
            ws.title = DOMUtil.getSimpleElementText( entry, "title" );
            Element cellLink = DOMUtil.getElementMatching(
                entry, "link", "rel", CELLSFEED );
            ws.cellFeedURL = cellLink.getAttribute( "href" );

            worksheets.add(ws);
        }
        return worksheets;
    }

    /**
     * We infer the list URL from the cell URL...
     * This is not hard to do, but I'm not sure if it's future proof.
     */
    public static String cellFeedURLtoListFeedURL( String cellURL ) {
        QuickAdd.out("start cfutlfu");
        //           http://spreadsheets.google.com/feeds/list/key/worksheetId/private/full
        // postURL = http://spreadsheets.google.com/feeds/list/o1092xxxxx5030349530.6403xxxxx7775707849/od6/private/full
        // cellURL = http://spreadsheets.google.com/feeds/cells/o109xxxxx485030349530.6403xxxxx7775707849/od6/private/full
        // don't have replace() in 1.4
        int i = cellURL.indexOf("feeds/cells");
        QuickAdd.out("i ="+i);
        return cellURL.substring(0,i) + "feeds/list" + cellURL.substring(i+11);
    }

    public static void postRow(
        Auth auth, List/*<String>*/columnNames, List values, String cellFeedURL, RowPostedListener listener )
    {
        try {
            postRowImpl( auth, columnNames, values, cellFeedURL, listener );
        } catch ( Exception e )
        {
            QuickAdd.out(e.toString());
            listener.rowPostedError();
        }
    }

    private static void postRowImpl(
        Auth auth, List/*<String>*/columnNames, final List values, String cellFeedURL, final RowPostedListener listener )
    {
        listener.rowBeginPosting();
        QuickAdd.out("postRow..............");
        String listFeedPostURL = cellFeedURLtoListFeedURL( cellFeedURL );
        QuickAdd.out( "values = " + values );
        QuickAdd.out( "postURL = " + listFeedPostURL );
        //String postEntry =
        //    "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>" +
        //    "<gsx:headerone>1</gsx:headerone>" +
        //    "<gsx:headertwo>2</gsx:headertwo>" +
        //    "<gsx:headerthree>3</gsx:headerthree>" +
        //    "</entry>";

        StringBuffer postEntry = new StringBuffer();
        postEntry.append(
            "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>" );
        for ( int i = 0; i < values.size(); i++ )
        {
            String name = (String)columnNames.get(i);

            String value = (String)values.get( i );
            // empty cell, skip it
            if ( value == null || value.equals("") )
                continue;

            postEntry.append("<gsx:"+toGSXName(name)+">");
            postEntry.append(value);
            postEntry.append("</gsx:"+toGSXName(name)+">");
        }
        postEntry.append("</entry>");
        QuickAdd.out("post = "+postEntry);

        ResponseTextHandler handler = new XMLResponseHandler() {
            public void handle( Element response )
            {
                // quick hack to examine it as a string...
                String responseText = response.toString();
                if ( isRowPostResponseOk(responseText ) )
                    listener.rowPostedOk( values );
                else
                    listener.rowPostedError();
            }

            public void handleError( String responseText )
            {
                listener.rowPostedError();
            }
        };
        HttpUtil.doAuthorizedPost( listFeedPostURL, ATOM_CONTENT_TYPE, auth.getToken(), postEntry.toString(), handler );
    }

    /**
     * Try to convert the column name to an acceptable element name.
     * GData simply skips spaces, &s, etc. and concatenates.
     * This is kind of riciculous.
     * @param s
     * @return
     */
    private static String toGSXName( String s ) {
        StringBuffer sb = new StringBuffer();
        for(int i=0; i<s.length(); i++) {
            char c = s.charAt( i );
            if ( c!=' ' && c!='&' )
                sb.append( Character.toLowerCase( c ) );
        }
        return sb.toString();
    }

    /* e.g.
        response = <?xml version='1.0' encoding='UTF-8'?><entry xmlns='http://www.w3.org/2005/Atom'
        xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>
        <id>http://spreadsheets.google.com/feeds/list/o10922066485030349530.6403441927775707849/od6/private/full/bxw30</id>
        <updated>2007-03-02T23:57:05.570Z</updated>
        <category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#list'>
        </category><title type='text'>ferere</title><content type='text'>HeaderTwo: doug, HeaderThree: bob</content>
        <link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o10922066485030349530.6403441927775707849/od6/private/full/bxw30'>
        </link><link rel='edit' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o10922066485030349530.6403441927775707849/od6/private/full/bxw30/gj7rb4dw01'></link>
        <gsx:headerone>ferere</gsx:headerone><gsx:headertwo>doug</gsx:headertwo><gsx:headerthree>bob</gsx:headerthree></entry>
    */
    /**
     * Try to determine if the response says that the cells were posted ok.
     */
    private static boolean isRowPostResponseOk( String response ) {
        return response.indexOf("Atom") != -1
            && response.indexOf("<updated>") != -1
            && response.indexOf("gsx:") != -1;
    }

}
